/////////////////////////////////////////////////////////////////////////
// MIT License
//
// Copyright (c) 2000-2011 OpenNETCF Consulting, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
////////////////////////////////////////////////////////////////////////

using System;
using System.Reflection;
using System.Drawing;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;

namespace OpenNETCF.Windows.Forms.Calendar
{
	/// <summary>
	/// Summary description for MonthView.
	/// </summary>
	public class MonthCalendar : BaseCalendar
	{
		#region Private data
		
		private Size m_CellSize;
		private Rectangle m_rActiveRect;
        private bool keepSelectedDayFocused = true;

    

		#endregion

        /// <summary>
        /// Month calendar control giving a monthly visual representation.
        /// </summary>
		public MonthCalendar():base()
		{
			InitializeComponent();


			m_CellSize = new Size(34, 38);
			m_weekBarHeight = 16;
			
			SelectedDate = DateTime.Now.Date;
			Size = new Size(240, 266);

		}

        /// <summary>
        /// Determines where the X,Y co-ordinates are located within the control
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
		public MonthCalendar.HitTestInfo HitTest(int x, int y)
		{
			HitTestInfo hti = new HitTestInfo();
			hti.Location = new Point(x, y);
			hti.Type = HitTestType.Nowhere;
			if ( m_rTopBar.Contains(x, y) )
			{
				hti.Type = HitTestType.Month;
				return hti;
			}

			Rectangle r = new Rectangle((Width - m_CellSize.Width * 7) / 2, m_rTopBar.Bottom - m_CellSize.Height, m_CellSize.Width * 7, m_CellSize.Height);
			DateTime dateStart = new DateTime(Date.Year, Date.Month, 1);
			while( dateStart.DayOfWeek != DayOfWeek.Sunday )
				dateStart = dateStart.AddDays(-1);
			for( int i = 0; i < 6; i ++ )
			{
				r.Offset(0, m_CellSize.Height);
				if ( r.Contains(x, y) )
				{
					if ( y == r.Top || y == r.Bottom )
					{
						hti.Type = HitTestType.Grid;
						return hti;
					}
					Rectangle rCell = new Rectangle(r.X, r.Y, m_CellSize.Width, m_CellSize.Height);
					for( int j = 0; j < 7; j++ )
					{
						if ( rCell.Contains(x, y) )
						{
							if ( x == rCell.Left || x == rCell.Right )
							{
								hti.Type = HitTestType.Grid;
								return hti;
							}
							DateTime dtThis = dateStart.AddDays(i * 7 + j);
							hti.Date = dtThis;
							if ( dtThis.Month == Date.Month )
							{
								hti.Type = HitTestType.DayThisMonth;
								return hti;
							}
							else
							{
								hti.Type = HitTestType.DayOtherMonth;
								return hti;
							}
						}
						rCell.Offset(m_CellSize.Width, 0);
					}
				}
			}
			return hti;
		}


		#region Component Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
		}
		#endregion

        //protected override void OnMonthFontChanged()
        //{
        //}

        /// <summary>
        /// Raises the System.Windows.Forms.Control.MouseDown event
        /// </summary>
        /// <param name="e"></param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			HitTestInfo hti = HitTest(e.X, e.Y);
			if( hti.Type == HitTestType.DayOtherMonth || hti.Type == HitTestType.DayThisMonth )
			{
                //Invalidate the other day to remove the focus square
                if (this.KeepSelectedDayFocused)
                    this.Invalidate(m_rActiveRect);
				Rectangle rCell;
				CellFromPoint(new Point(e.X, e.Y), out rCell);
				this.m_rCellMoving = m_rActiveRect = rCell;
				rCell.Inflate(-1, -1);
                if (this.KeepSelectedDayFocused)
                    this.Invalidate(m_rActiveRect);

				Capture = true;
			}
			base.OnMouseDown (e);
		}

        /// <summary>
        /// Raises the System.Windows.Forms.Control.MouseUp event
        /// </summary>
        /// <param name="e"></param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
			HitTestInfo hti = HitTest(e.X, e.Y);
			if( hti.Type == HitTestType.DayOtherMonth || hti.Type == HitTestType.DayThisMonth )
			{
                Rectangle temp = this.m_rActiveRect;
                this.m_rActiveRect = this.m_rCellMoving;
                //Invalidate the moving cell which is now the active cell
                this.Invalidate(this.m_rCellMoving);
                //invaludate the old active cell to remove the highlight
                this.Invalidate(temp);

				OnDateSelected(hti.Date);
				Rectangle rCell;
				CellFromPoint(new Point(e.X, e.Y), out rCell);
                if (!this.KeepSelectedDayFocused)
				    Invalidate(rCell);
				Capture = false;
			}
			base.OnMouseUp (e);
		}

        private Rectangle m_rCellMoving;

        /// <summary>
        /// Raises the System.Windows.Forms.Control.MouseMove event
        /// </summary>
        /// <param name="e"></param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
			if ( Capture )
			{
				HitTestInfo hti = HitTest(e.X, e.Y);
				if( hti.Type == HitTestType.DayOtherMonth || hti.Type == HitTestType.DayThisMonth )
				{
					Rectangle rCell;
					CellFromPoint(new Point(e.X, e.Y), out rCell);
					if ( !rCell.Equals(m_rActiveRect) )
					{
                        if (!rCell.Equals(this.m_rCellMoving))
                        {
                            Invalidate(m_rCellMoving);
                            m_rCellMoving = rCell;
                            Graphics g = CreateGraphics();
                            rCell.Inflate(-1, -1);
                            g.DrawRectangle(m_penGrid, rCell);
                            g.Dispose();
                        }
					}
				}
				else
				{
					Invalidate(m_rActiveRect);
				}
			}
			base.OnMouseMove (e);
		}

        /// <summary>
        /// Raises the System.Windows.Forms.Control.Resize event
        /// </summary>
        /// <param name="e"></param>
		protected override void OnResize(EventArgs e)
		{
			m_weekBarHeight = (int)(FontMonth.Size * 2) * this.ScaleSize;
            m_CellSize = new Size(Size.Width / 7,(Size.Height - (m_weekBarHeight + m_btnRight.Height + 2)) / 6);
            m_leftOffset = ((Width - m_CellSize.Width * 7) / 2);
            m_rTopBar = new Rectangle(0, 0, Width, (m_weekBarHeight + m_btnRight.Height + 2));
            m_btnRight.Location = new Point(Width - m_btnRight.Width - 4, 1);
			m_btnLeft.Location = new Point(m_btnRight.Left - m_btnLeft.Width - 3, m_btnRight.Top);
			base.OnResize (e);
		}

        private DateTime selectedDate;

        internal override void btnMonth_Click(object sender, EventArgs e)
        {
            base.btnMonth_Click(sender, e);
            this.PaintSelectedDate();
        }
        internal override void btnToday_Click(object sender, EventArgs e)
        {
            base.btnToday_Click(sender, e);
            this.PaintSelectedDate();
        }
        internal override void btnYear_Click(object sender, EventArgs e)
        {
            base.btnYear_Click(sender, e);
            this.PaintSelectedDate();
        }
		internal override void btnRight_Click(object sender, EventArgs e)
		{
			Date = Date.AddMonths(1);
            this.PaintSelectedDate();
		}

		internal override void btnLeft_Click(object sender, EventArgs e)
		{
            Date = Date.AddMonths(-1); 
            this.PaintSelectedDate();
		}

        /// <summary>
        /// Raises the OpenNETCF.Windows.Forms.Calendar.MonthCalendar.DateSelected event
        /// </summary>
        /// <param name="newDate"></param>
        protected override void OnDateSelected(DateTime newDate)
        {
            this.SelectedDate = newDate;
        }

        /// <summary>
        /// Gets or sets which controls are docked to it's parent controls and determines how a control is resized with its parent
        /// </summary>
        public override DockStyle Dock
        {
            get
            {
                return base.Dock;
            }
            set
            {
                base.Dock = value;
            }
        }

        /// <summary>
        /// Gets or sets the backcolor of the control
        /// </summary>
        public override Color BackColor
        {
            get
            {
                return base.BackColor;
            }
            set
            {
                base.BackColor = value;
                if ( m_btnLeft != null ) m_btnLeft.BackColor = value;
                if (m_btnRight != null) m_btnRight.BackColor = value;
                if (m_btnMonth != null) m_btnMonth.BackColor = value;
                if (m_btnYear != null) m_btnYear.BackColor = value;
            }
        }

        /// <summary>
        /// Gets or set the value indicating if the selected date should be focused
        /// </summary>
        public bool KeepSelectedDayFocused
        {
            get { return this.keepSelectedDayFocused; }
            set
            {
                if (this.keepSelectedDayFocused != value)
                {
                    this.keepSelectedDayFocused = value;
                    this.Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or sets the selected date for the control
        /// </summary>
        public DateTime SelectedDate
        {
            get
            {
                return this.selectedDate;
            }
            set
            {
                this.selectedDate = Date = value;
                this.PaintSelectedDate();
                base.OnDateSelected(value);
            }
        }

		#region Private methods

		#region Painting
        /// <summary>
        /// Draws the month calendar control
        /// </summary>
        /// <param name="g"></param>
        /// <param name="rClip"></param>
		protected override void DrawControl(Graphics g, Rectangle rClip)
		{
            if (Date.Year == 1)
                return;

			g.Clear(BackColor);
			Point [] Triangle = new Point[3];
			
			// Paint month bar if needed
            if ( m_rTopBar.IntersectsWith(rClip) )
            {
				m_btnMonth.Text = Date.Date.ToString("MMMM");
				m_btnYear.Text = Date.Date.ToString("yyyy");
				SizeF szMonth = g.MeasureString(m_btnMonth.Text, FontMonth);
				SizeF szYear = g.MeasureString(m_btnYear.Text, FontMonth);
				szYear.Height += 4;
				szYear.Width += 4;
				szMonth.Height += 4;
				szMonth.Width += 4;
				m_btnMonth.Location = new Point(4, 2);
				m_btnMonth.Size = new Size((int)szMonth.Width, (int)szMonth.Height);
				m_btnYear.Location = new Point(m_btnMonth.Right + 3, m_btnMonth.Top);
				m_btnYear.Size = new Size((int)szYear.Width, (int)szYear.Height);
				
				// Draw weekday names
				for( int i = 0; i < 7; i++ )
				{
                    SolidBrush brush = this.m_brMonthFG;
                    if (i == (int)DayOfWeek.Sunday)
                        brush = this.m_brSundayFG;
                    else if (i == (int)DayOfWeek.Saturday)
                        brush = this.m_brSaturdayFG;

                    g.DrawString(((DayOfWeek)i).ToString().Substring(0, 1), FontMonth, brush, i * m_CellSize.Width + m_CellSize.Width * .5f - 4, m_rTopBar.Bottom - m_weekBarHeight + 2);
				}
				g.DrawRectangle(m_penGrid, m_rTopBar);
				g.DrawLine(m_penGrid, m_rTopBar.Left, m_rTopBar.Bottom - m_weekBarHeight, m_rTopBar.Width, m_rTopBar.Bottom - m_weekBarHeight);
				g.DrawLine(m_penGrid, m_btnLeft.Right + 2, m_btnLeft.Top, m_btnLeft.Right + 2, m_btnLeft.Bottom);
            }

			Rectangle r = new Rectangle(m_leftOffset, m_rTopBar.Bottom - m_CellSize.Height, m_CellSize.Width * 7, m_CellSize.Height);

			// Paint each row of cells

			//Calculate start date
			DateTime dateStart = new DateTime(Date.Year, Date.Month, 1);
			while( dateStart.DayOfWeek != DayOfWeek.Sunday )
				dateStart = dateStart.AddDays(-1);
			
			// Paint 7 x 6 grid
			for( int i = 0; i < 6; i ++ )
			{
				r.Offset(0, m_CellSize.Height);
				// Ignore rows that do not have to be repainted, not even partially
				if (! r.IntersectsWith(rClip))
					continue;
				// Draw row frame (border)
				g.DrawRectangle(m_penGrid, r);
				Rectangle rCell = new Rectangle(r.X, r.Y, m_CellSize.Width, m_CellSize.Height);
				//
				for( int j = 0; j < 7; j++ )
				{
					if ( rCell.IntersectsWith(rClip) )
					{
						RectangleF rCellF = new RectangleF(rCell.X, rCell.Y, m_CellSize.Width, m_CellSize.Height);
						DateTime dtThis = dateStart.AddDays(i * 7 + j);

                        //Determin if it's the selected cell
                        bool selectedCell = rCell.IntersectsWith(this.m_rActiveRect) && this.keepSelectedDayFocused;

                        if(selectedCell)
                            g.FillRectangle(this.brush_SelectedDayBackColor, rCell);
                        else
						    g.FillRectangle(dtThis.Month == Date.Month? m_brDayBG: m_brBackground, rCell);

                        //Draw text
                        SolidBrush textBrush = this.m_brDayFG;
                        if (selectedCell)
                            textBrush = this.brush_SelectedDayForeColor;
                        else if (j == (int)DayOfWeek.Sunday)
                            textBrush = this.m_brSundayFG;
                        else if (j == (int)DayOfWeek.Saturday)
                            textBrush = this.m_brSaturdayFG;
                        g.DrawString(dtThis.Day.ToString(), Font, textBrush, rCell.X + 3, rCell.Y + 2);
						
                        // If label painting delegate is specified - use it
						if ( !OnDrawLabel(dtThis, rCell, g) )
						{
							string sLabel = OnGetLabel(dtThis.Date);
							if ( sLabel.Length > 0 )
							{
								if ( rCell.Width < rCell.Height )
								{
									g.FillRectangle(m_brLabelBG, rCell.X + 1, rCell.Y + rCell.Height / 2, rCell.Width - 1, rCell.Bottom - rCell.Y + rCell.Height / 2); 
									rCellF.X += rCell.Width * .3f;
									rCellF.Y += rCell.Height / 2f;
									rCellF.Width -= rCellF.Width * .3f;
									rCellF.Height = rCellF.Height / 2;
								}
								else
								{
									g.FillRectangle(m_brLabelBG, rCell.X + rCell.Width / 2, rCell.Y + 1, rCell.Width / 2, rCell.Height - 1); 
									rCellF.X += rCell.Width / 2f;
									rCellF.Y += rCell.Height * .3f;
									rCellF.Width = rCellF.Width / 2;
									rCellF.Height = rCellF.Height * .6f;
								}
								g.DrawString(sLabel, m_fntLabel, m_brLabelFG, rCellF);
							}
						}
						g.DrawRectangle(m_penGrid, rCell);
					}
					rCell.Offset(m_CellSize.Width, 0);
				}
			}
			g.DrawRectangle(m_penGrid, 0, 0, Width - 1, Height - 1);
			if ( GetCellRect(DateTime.Now, out rClip) )
				g.DrawRectangle(new Pen(Color.Red), rClip );

#if EVAL_VERSION
            UIUtils.PaintWatermark(g, this.Width, this.Height);
#endif

		}

        #endregion

        private void PaintSelectedDate()
        {
            GetCellRect(this.SelectedDate, out this.m_rActiveRect);
            Invalidate(this.m_rActiveRect);
        }

		private bool GetCellRect(DateTime cellDate, out Rectangle rCell)
		{
			rCell = new Rectangle(0, 0, 0, 0);
            if (Date.Year == 1)
                return false;
			DateTime dateStart = new DateTime(Date.Year, Date.Month, 1);
			while( dateStart.DayOfWeek != DayOfWeek.Sunday )
				dateStart = dateStart.AddDays(-1);
			int timeDiff = (int)cellDate.Subtract(dateStart).TotalDays;
			if ( timeDiff < 0 || timeDiff >= 42 )
				return false;
			rCell = new Rectangle((Width - m_CellSize.Width * 7) / 2, m_rTopBar.Bottom, m_CellSize.Width, m_CellSize.Height);
			rCell.Offset( m_CellSize.Width * (timeDiff % 7), m_CellSize.Height * (timeDiff / 7) );
			return true;
		}

		private bool CellFromPoint(Point pt, out Rectangle rect)
		{
			rect = new Rectangle(0, 0, 0, 0);
			if ( pt.X < m_leftOffset || pt.X > m_leftOffset + m_CellSize.Width * 7 )
				return false;
			if ( pt.Y < m_rTopBar.Bottom || pt.Y > m_rTopBar.Bottom + m_CellSize.Height * 6 )
				return false;
			rect.Width = m_CellSize.Width;
			rect.Height = m_CellSize.Height;
			rect.X = m_leftOffset + (pt.X - m_leftOffset) / m_CellSize.Width * m_CellSize.Width;
			rect.Y = m_rTopBar.Bottom + (pt.Y - m_rTopBar.Bottom) / m_CellSize.Height * m_CellSize.Height;
			return true;
		}

		#endregion

		#region IDisposable Members
        /// <summary>
        /// Releases the unmanaged resources of OpenNETCF.Windows.Forms.Calendar.MonthCalendar control
        /// </summary>
		public new void Dispose()
		{
			base.Dispose(true);
			if ( m_gxOff != null )
				m_gxOff.Dispose();
			if ( m_bmOff != null )
				m_bmOff.Dispose();
		}

		#endregion

        /// <summary>
        /// Describes hit test results for the calendar control
        /// </summary>
		public struct HitTestInfo
		{
            /// <summary>
            /// The type of hit test
            /// </summary>
			public HitTestType Type;
            /// <summary>
            /// The location of the hit test
            /// </summary>
			public Point Location;
            /// <summary>
            /// The date associated with the hittest
            /// </summary>
			public DateTime Date;
		}

        /// <summary>
        /// Defines the type of hit test that resulted from an X,Y co-ordinates
        /// </summary>
		public enum HitTestType
		{
            /// <summary>
            /// Nothing was hit
            /// </summary>
			Nowhere,
            /// <summary>
            /// Month item was hit
            /// </summary>
			Month,
            /// <summary>
            /// Hours item was hit
            /// </summary>
			Hours,
            /// <summary>
            /// Day this month was hit
            /// </summary>
			DayThisMonth,
            /// <summary>
            /// Day in another month was hit
            /// </summary>
			DayOtherMonth,
            /// <summary>
            /// The grid was hit
            /// </summary>
			Grid
		}

	}

}
